<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI流式对话演示</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3B82F6',
                        secondary: '#10B981',
                        neutral: '#1F2937',
                        'neutral-light': '#F3F4F6',
                        danger: '#EF4444',
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>

    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .typing-animation {
                display: inline-block;
                width: 1em;
                overflow: hidden;
                animation: typing 1.5s infinite;
            }
            @keyframes typing {
                0%, 100% { width: 1em; }
                50% { width: 0.5em; }
            }
            .message-appear {
                animation: fadeIn 0.3s ease-out;
            }
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            .formatted-text p {
                margin-bottom: 0.5rem;
            }
            .formatted-text br {
                content: "";
                margin: 0.25rem 0;
                display: block;
            }
            /* 底部固定区域样式 */
            .bottom-fixed {
                position: fixed;
                bottom: 0;
                left: 0;
                right: 0;
                background-color: white;
                padding: 1rem;
                box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
                z-index: 100;
            }
            /* 对话区域避免被底部遮挡 */
            .chat-container {
                margin-bottom: 140px; /* 适配底部固定区域高度 */
            }
        }
    </style>
</head>
<body class="bg-gray-50 font-sans text-neutral">
<div class="container mx-auto px-4 py-8 max-w-4xl">
    <!-- 页面标题 -->
    <header class="mb-8 text-center">
        <h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-primary mb-2">
            <i class="fa fa-comments-o mr-2"></i>AI流式对话
        </h1>
        <p class="text-gray-500">体验实时生成的AI回复，感受流畅的对话体验</p>
    </header>

    <!-- 对话显示区域（避免被底部遮挡） -->
    <div class="bg-white rounded-lg shadow-md p-4 mb-6 min-h-[400px] chat-container" id="chatContainer">
        <div id="html-container" class="space-y-6 message-appear">
            <!-- 初始提示信息 -->
            <div class="flex items-start gap-3">
                <div class="bg-primary/10 p-2 rounded-full text-primary">
                    <i class="fa fa-robot text-xl"></i>
                </div>
                <div class="bg-neutral-light p-4 rounded-lg rounded-tl-none max-w-[85%] formatted-text">
                    <p>你好！我是AI助手，有什么可以帮助你的吗？请输入问题并点击发送按钮。</p>
                </div>
            </div>
        </div>

        <!-- 加载状态指示器 -->
        <div id="loadingIndicator" class="hidden flex items-start gap-3 mt-4">
            <div class="bg-primary/10 p-2 rounded-full text-primary">
                <i class="fa fa-robot text-xl"></i>
            </div>
            <div class="bg-neutral-light p-4 rounded-lg rounded-tl-none max-w-[85%]">
                <span class="typing-animation">▌</span>
            </div>
        </div>
    </div>

    <!-- 状态信息区域 -->
    <div class="text-center text-sm text-gray-500 mb-4">
        <p id="statusInfo">准备就绪</p>
    </div>

    <!-- 底部固定区域（输入+停止按钮） -->
    <div class="bottom-fixed">
        <div class="max-w-4xl mx-auto">
            <div class="bg-white rounded-lg shadow-md p-4">
                <div class="flex gap-3">
                    <input
                            type="text"
                            id="messageInput"
                            placeholder="请输入你的问题..."
                            class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
                    >
                    <button
                            id="sendButton"
                            class="bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg font-medium transition-all flex items-center gap-2"
                    >
                        <span>发送</span>
                        <i class="fa fa-paper-plane"></i>
                    </button>
                    <button
                            id="stopButton"
                            class="hidden bg-danger hover:bg-danger/90 text-white px-6 py-3 rounded-lg font-medium transition-all flex items-center gap-2"
                    >
                        <span>停止</span>
                        <i class="fa fa-stop"></i>
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- 页脚（被底部固定区域覆盖，可省略或调整位置） -->
    <footer class="text-center text-gray-400 text-sm mt-8">
        <p>© 2024 AI流式对话演示 | 使用现代Web技术构建</p>
    </footer>
</div>

<script>
    // DOM元素引用
    const messageInput = document.getElementById('messageInput');
    const sendButton = document.getElementById('sendButton');
    const stopButton = document.getElementById('stopButton');
    const htmlContainer = document.getElementById('html-container');
    const loadingIndicator = document.getElementById('loadingIndicator');
    const statusInfo = document.getElementById('statusInfo');
    let abortController = null; // 用于中断请求的控制器
    let isStreaming = false; // 是否正在流式响应

    // 初始化 - 为输入框添加回车发送功能
    messageInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter' && !isStreaming) {
            sendMessage();
        }
    });

    // 发送按钮点击事件
    sendButton.addEventListener('click', () => {
        if (!isStreaming) {
            sendMessage();
        }
    });

    // 停止按钮点击事件（中断流式响应）
    stopButton.addEventListener('click', () => {
        if (isStreaming && abortController) {
            abortController.abort('用户手动停止'); // 中断请求
            stopStreaming(); // 清理状态
        }
    });

    // 发送消息函数
    function sendMessage() {
        const message = messageInput.value.trim();
        if (!message) return;

        // 清空输入框
        messageInput.value = '';

        // 添加用户消息到对话区域
        addUserMessage(message);

        // 开始流式获取AI回复
        handleFluxHtmlStream(message);
    }

    // 添加用户消息到界面
    function addUserMessage(message) {
        const userMessageHtml = `
                <div class="flex items-start gap-3 justify-end message-appear">
                    <div class="bg-primary text-white p-4 rounded-lg rounded-tr-none max-w-[85%] formatted-text">
                        <p>${escapeHtml(message)}</p>
                    </div>
                    <div class="bg-gray-200 p-2 rounded-full text-gray-600">
                        <i class="fa fa-user text-xl"></i>
                    </div>
                </div>
            `;
        htmlContainer.innerHTML += userMessageHtml;
        scrollToBottom();
    }

    // 处理流式HTML响应（解析JSON+格式化+固定底部）
    async function handleFluxHtmlStream(message) {
        // 初始化状态
        isStreaming = true;
        abortController = new AbortController(); // 创建中断控制器
        sendButton.classList.add('hidden');
        stopButton.classList.remove('hidden');
        statusInfo.textContent = 'AI正在思考...';
        loadingIndicator.classList.remove('hidden');
        scrollToBottom();

        try {
            const response = await fetch(
                `http://localhost:8080/ai/chat/ai/generateStreamDocByMyClient?message=${encodeURIComponent(message)}`,
                {
                    method: 'GET',
                    headers: {
                        'Accept': 'text/event-stream'
                    },
                    signal: abortController.signal ,// 关联中断信号
                    // 关键配置：允许跨域请求携带Cookie（包括JSESSIONID）
                    credentials: 'include'
                }
            );

            if (!response.ok) {
                throw new Error(`请求失败: ${response.status} ${response.statusText}`);
            }

            // 隐藏加载指示器，显示AI回复容器
            loadingIndicator.classList.add('hidden');
            const aiMessageId = `ai-message-${Date.now()}`;
            htmlContainer.innerHTML += `
            <div class="flex items-start gap-3 message-appear">
                <div class="bg-primary/10 p-2 rounded-full text-primary">
                    <i class="fa fa-robot text-xl"></i>
                </div>
                <div id="${aiMessageId}" class="bg-neutral-light p-4 rounded-lg rounded-tl-none max-w-[85%] formatted-text">
                    <p></p>
                </div>
            </div>
        `;
            const aiMessageContainer = document.getElementById(aiMessageId);
            const aiParagraph = aiMessageContainer.querySelector('p');
            scrollToBottom();

            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let buffer = ''; // 累积所有接收的字符
            let accumulatedText = '';
            const TYPE_SPEED = 50;

            while (true) {
                const { done, value } = await reader.read();
                if (done) break;

                // 1. 解码并累积数据到缓冲区
                buffer += decoder.decode(value, { stream: true });

                // 2. 按换行符分割缓冲区，逐行处理
                const lines = buffer.split('\n');
                buffer = lines.pop() || ''; // 最后一行可能不完整，留到下次处理

                // 3. 处理每一行
                for (const line of lines) {
                    const trimmedLine = line.trim();
                    if (!trimmedLine) continue; // 跳过空行

                    // 4. 去除'data:'前缀（兼容带空格的情况，如'data: '）
                    const content = trimmedLine.replace(/^data:\s*/, '');
                    if (!content) continue; // 跳过仅含'data:'的空行
                    try {
                        // 5. 尝试解析为JSON（优先处理JSON格式）
                        const jsonData = JSON.parse(content);
                        // 从JSON中提取text字段（兼容之前的格式）
                        const aiText = jsonData.result?.output?.text || '';
                        if (aiText) {
                            accumulatedText += aiText.replace(/\n/g, '<br>');
                        }else{
                            accumulatedText += content.replace(/\n/g, '<br>');
                        }
                    } catch (jsonErr) {
                        // 6. 解析JSON失败，视为纯文本直接拼接（处理如"十一"、"Do you..."等）
                        accumulatedText += content.replace(/\n/g, '<br>');
                    }

                    // 7. 更新UI显示
                    aiParagraph.innerHTML = accumulatedText;
                    scrollToBottom();
                    await new Promise(resolve => setTimeout(resolve, TYPE_SPEED));
                }
            }

            statusInfo.textContent = '回复完成';
        } catch (err) {
            // 忽略用户主动中断的错误
            if (err.name !== 'AbortError') {
                console.error('处理流出错:', err);
                htmlContainer.innerHTML += `
                    <div class="flex items-start gap-3 message-appear">
                        <div class="bg-primary/10 p-2 rounded-full text-primary">
                            <i class="fa fa-robot text-xl"></i>
                        </div>
                        <div class="bg-red-50 text-red-600 p-4 rounded-lg rounded-tl-none max-w-[85%]">
                            <p>抱歉，处理请求时出错: ${escapeHtml(err.message)}</p>
                        </div>
                    </div>
                `;
                statusInfo.textContent = `错误: ${err.message}`;
            } else {
                statusInfo.textContent = '已停止回复';
            }
        } finally {
            stopStreaming(); // 清理状态
        }
    }

    // 停止流式响应并清理状态
    function stopStreaming() {
        isStreaming = false;
        abortController = null;
        sendButton.classList.remove('hidden');
        stopButton.classList.add('hidden');
        loadingIndicator.classList.add('hidden');
        scrollToBottom();
    }

    // 滚动到最新消息
    function scrollToBottom() {
        const chatContainer = document.getElementById('chatContainer');
        chatContainer.scrollTop = chatContainer.scrollHeight;
    }

    // HTML转义函数（防止XSS攻击）
    function escapeHtml(unsafe) {
        if (!unsafe) return '';
        return unsafe
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }
</script>
</body>
</html>